/*
 * UpdateLinksAction.java
 *
 * Created on July 15, 2007, 11:20 PM
 *
 * Copyright 2008 David D. Emory
 * 
 * This file is part of Five Points. See <http://www.fpdev.org> for
 * additional information regarding the project.
 * 
 * Five Points is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Five Points is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Five Points.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.fpdev.apps.admin.actions;

import java.awt.geom.Point2D;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.fpdev.core.data.LegacyDB;
import org.fpdev.core.basenet.BaseNetGraph;
import org.fpdev.core.basenet.BLink;
import org.fpdev.core.basenet.BLinkStreet;
import org.fpdev.core.basenet.BNode;
import org.fpdev.core.data.SpatialDataConnection.LinkAttr;
import org.fpdev.apps.admin.AdminClient;

/**
 *
 * @author demory
 */
public class EditLinksPropertiesAction implements ACAction {

  // store links as Integer IDs rather than BLink instances to avoid
  // confusion over possible BLink class-type chages
  private Set<Integer> linkIDs_;

  private Map<Integer, LinkInfo> oldInfo_;
  private Integer newType_,  newTrnProv_,  newBPProv_;
  private String newName_;
  private Direction dir_;
  private BNode oneWayOrigin_;

  public enum Direction { NO_CHANGE, ONE_WAY, TWO_WAY }

  /** Creates a new instance of EditLinksPropertiesAction */
  public EditLinksPropertiesAction(Set<BLink> links, Integer newType, Integer newTrnProv, Integer newBPProv, String newName, Direction dir, BNode oneWayOrigin) {
    
    // convert link set to ID set:
    linkIDs_ = new HashSet<Integer>();
    for (Iterator<BLink> linkIter = links.iterator(); linkIter.hasNext();)
      linkIDs_.add(linkIter.next().getID());

    newType_ = newType;
    newTrnProv_ = newTrnProv;
    newBPProv_ = newBPProv;
    newName_ = newName;
    dir_ = dir;
    oneWayOrigin_ = oneWayOrigin;

    // save initial properties:
    oldInfo_ = new HashMap<Integer, LinkInfo>();
    for (Iterator<BLink> linkIter = links.iterator(); linkIter.hasNext();) {
      BLink link = linkIter.next();
      LinkInfo info = new LinkInfo();
      info.type = link.getType();
      info.fnode = link.getFNode();
      info.name = link.getDisplayName();
      if (link.getClassType() == BLink.CLASS_STREET) {
        info.trnProv = ((BLinkStreet) link).getTransitProv();
        info.bpProv = ((BLinkStreet) link).getBikePedProv();
      } else {
        info.bpProv = info.trnProv = 0;
      }
      oldInfo_.put(link.getID(), info);
    }
  }

  public String getName() {
    return "Edit Link Properties";
  }

  private void updateLinksInDB(AdminClient av) { // does not handle shape pts or addr ranges
    LegacyDB db = av.getEngine().getDB();

    Set<BLink> linkSet = av.getEngine().getBaseNet().getLinkSetFromIDSet(linkIDs_);
    LinkAttr[] attrs = { LinkAttr.SHAPE, LinkAttr.TYPE, LinkAttr.NAME, LinkAttr.FNODE, LinkAttr.TNODE, LinkAttr.FADDRL, LinkAttr.FADDRR, LinkAttr.TADDRL, LinkAttr.TADDRR, LinkAttr.ZIPL, LinkAttr.ZIPR};
    av.getEngine().getDataPackage().getSpatialDataConn().modifyLinks(linkSet, attrs);

    Set<BNode> nodes = new HashSet<BNode>();
    Iterator<BLink> links = linkSet.iterator();
  }


  public boolean doAction(AdminClient ac) {

    for (Iterator<BLink> links = ac.getEngine().getBaseNet().getLinkSetFromIDSet(linkIDs_).iterator(); links.hasNext();) {
      BLink link = links.next();

      if (newType_ != null)
        link = ac.getEngine().getBaseNet().changeLinkType(link, newType_, true);

      if (newName_ != null)
        link.setName(newName_);
      
      if (link.getClassType() == BLink.CLASS_STREET && newTrnProv_ != null)
        ((BLinkStreet) link).setTransitProv(newTrnProv_);
      
      if (link.getClassType() == BLink.CLASS_STREET && newBPProv_ != null)
        ((BLinkStreet) link).setBikePedProv(newBPProv_);
    }

    // handle link direction separately
    if (dir_ == Direction.ONE_WAY && oneWayOrigin_ != null) {
      makeLinksOneWay(oneWayOrigin_, ac);
    } else if (dir_ == Direction.TWO_WAY) {
      makeLinksTwoWay(ac);
    }

    // update main links table in db 
    updateLinksInDB(ac);

    return true;
  }

  public boolean undoAction(AdminClient ac) {
    for (Iterator<BLink> links = ac.getEngine().getBaseNet().getLinkSetFromIDSet(linkIDs_).iterator(); links.hasNext();) {
      BLink link = links.next();
      LinkInfo oldInfo = oldInfo_.get(link.getID());

      link = ac.getEngine().getBaseNet().changeLinkType(link, oldInfo.type);
      System.out.println("old type: " + oldInfo.type);

      link.setName(oldInfo.name);
      if (link.getClassType() == BLink.CLASS_STREET) { // && newTrnProv_ != null)
        ((BLinkStreet) link).setTransitProv(oldInfo.trnProv);
        ((BLinkStreet) link).setBikePedProv(oldInfo.bpProv);
      }
      if (oneWayOrigin_ != null && link.getFNode() != oldInfo.fnode) {
        link.swapEndPoints();
      }

    }

    // update main links table in db
    updateLinksInDB(ac);

    return true;
  }

  private void makeLinksOneWay(BNode start, AdminClient ac) {
    BaseNetGraph graph = new BaseNetGraph(ac.getEngine().getBaseNet().getLinkSetFromIDSet(linkIDs_).iterator());
    if (!graph.isSimplePath()) {
      ac.msg("Selected links must form continuous linear path");
      return;
    }


    BNode node = start;
    BLink link = graph.edgesOf(node).iterator().next();
    makeLinkOneWay(link, node);

    for (int i = 0; i < graph.edgeSet().size() - 1; i++) {
      node = graph.opposite(node, link);
      Set<BLink> links = new HashSet<BLink>(graph.edgesOf(node));
      links.remove(link);
      link = links.iterator().next();
      makeLinkOneWay(link, node);
    }

  }

  private void makeLinkOneWay(BLink link, BNode start) {

    if (link.getClassType() == BLink.CLASS_STREET) {
      ((BLinkStreet) link).makeOneWay();
    } // changes link type

    if (link.getTNode() == start) { // link must be reversed

      // swap end points in link object
      link.swapEndPoints();

      // if applicable, create EditShapePointsAction for reversed shp pts
      if (link.shapePointsCount() > 0) {
        List<Point2D.Double> copyToReverse = new LinkedList<Point2D.Double>(link.getShapePointsList());
        Collections.reverse(copyToReverse);
      }
    }

  }

  private void makeLinksTwoWay(AdminClient ac) {
    for (Iterator<Integer> ids = linkIDs_.iterator(); ids.hasNext();) {
      BLink link = ac.getEngine().getBaseNet().getLinkFromID(ids.next());
      if (link.getClassType() == BLink.CLASS_STREET) ((BLinkStreet) link).makeTwoWay();
    }
  }

  private class LinkInfo {

    int type, bpProv, trnProv;
    String name;
    BNode fnode;
  }
}
